//---------------------------------------------------------------------
// Author: jachymko
//
// Description: Type name cache 
//
// Creation Date: Jan 8, 2007
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;

using BGShell.Collections;
using BGShell.PowerShell.LanguageService;

namespace BGShell.PowerShell.TabExpansion.Caches
{
    internal sealed class TypeNameCache : TabExpansionCache
    {
        public static readonly TypeNameCache Instance = new TypeNameCache();

        private readonly TypeCacheLoader _loader;
        private readonly RootNode _cache;
        private readonly Node _system;

        private TypeNameCache()
        {
            _cache = new RootNode();
            _loader = new TypeCacheLoader(_cache);
            _system = _cache.AddNamespace("System");

            InitializeDefaultNodes();
        }

        public IEnumerable<String> Expand(string prefix)
        {
            EnsureInitialized();

            string childPrefix;
            string resultPrefix;

            var lastSeparator = prefix.LastIndexOf('.');

            if (lastSeparator < 0)
            {
                childPrefix = prefix;
                resultPrefix = string.Empty;
            }
            else
            {
                childPrefix = prefix.Substring(lastSeparator + 1);
                resultPrefix = prefix.Substring(0, lastSeparator);
            }

            if (resultPrefix.Length > 0)
            {
                resultPrefix += '.';
            }

            string[] parts = prefix.Split('.');

            return CombineEnumerable<String>
            (
                TryExpand(_cache, parts, childPrefix, resultPrefix),
                TryExpand(_system, parts, childPrefix, resultPrefix)
            );
        }

        internal override void OnInitialize()
        {
            AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad;

            foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
            {
                if (Stopped)
                {
                    break;
                }

                LoadAssembly(asm);
            }

            if (Stopped)
            {
                AppDomain.CurrentDomain.AssemblyLoad -= OnAssemblyLoad;
            }
        }

        private void OnAssemblyLoad(object sender, AssemblyLoadEventArgs e)
        {
            if (!Stopped)
            {
                LoadAssembly(e.LoadedAssembly);
            }
        }

        private void LoadAssembly(Assembly asm)
        {
            if (!(asm is System.Reflection.Emit.AssemblyBuilder))
            {
                _loader.Push(asm);
            }
        }

        private void InitializeDefaultNodes()
        {
            foreach (var t in PSLanguage.GetBuiltinTypes().Values)
            {
                _cache.Add(t);
            }
        }

        private static IEnumerable<String> TryExpand(Node root, string[] parts, string childPrefix, string resultPrefix)
        {
            Node n = root;

            for (int i = 0 ; i < parts.Length - 1 ; i++)
            {
                if (n == null) return null;

                n = n[parts[i]];
            }

            if (n == null) return null;

            return n.GetChildNames(childPrefix, resultPrefix);
        }

        private static IEnumerable<T> CombineEnumerable<T>(params IEnumerable<T>[] enumerables)
        {
            foreach (IEnumerable<T> collection in enumerables)
            {
                if (collection == null) continue;

                foreach (T item in collection)
                {
                    yield return item;
                }
            }
        }

        private sealed class TypeCacheLoader
        {
            private readonly RootNode _cache;
            private readonly InterlockedStack<IEnumerable<Type>> _pending = new InterlockedStack<IEnumerable<Type>>();

            private int _loadingCount;

            public TypeCacheLoader(RootNode cache)
            {
                _cache = cache;
            }

            public void Push(Assembly asm)
            {
                Interlocked.Increment(ref _loadingCount);

                _pending.Push(asm.GetExportedTypes());

                if (Interlocked.Decrement(ref _loadingCount) <= 0)
                {
                    do
                    {
                        var types = _pending.Pop();

                        if (types == null)
                        {
                            break;
                        }

                        lock (_cache.SyncRoot)
                        {
                            foreach(var t in types)
                            {
                                _cache.Add(t);
                            }
                        }
                    }
                    while (true);
                }
            }
        }

        private class Node
        {
            private readonly String _name;
            private readonly NodeKind _kind;

            private Dictionary<String, Node> _nodes;

            public Node(String name, NodeKind kind)
            {
                _name = name;
                _kind = kind;
            }

            public string Name
            {
                get { return _name; }
            }

            public NodeKind NodeKind
            {
                get { return _kind; }
            }

            public void Add(Type type)
            {
                Node n = this;

                if (type.Namespace != null)
                {
                    string[] ns = type.Namespace.Split('.');

                    for (int i = 0; i < ns.Length; i++)
                    {
                        n = n.EnsureNamespace(ns[i]);
                    }
                }

                var typeName = PSLanguage.FormatTypeName(type, true);


                n.Add(new Node(typeName, NodeKind.Type));
            }

            public void Add(string name)
            {
                Add(new Node(name, NodeKind.Type));
            }

            public Node this[string name]
            {
                get
                {
                    if (_nodes != null && _nodes.ContainsKey(name))
                        return _nodes[name];

                    return null;
                }
            }

            public IEnumerable<String> GetChildNames(string childPrefix, string resultPrefix)
            {
                foreach (string n in _nodes.Keys)
                {
                    if (n.StartsWith(childPrefix, StringComparison.OrdinalIgnoreCase))
                    {
                        if (_nodes[n].NodeKind == NodeKind.Namespace)
                        {
                            yield return '[' + resultPrefix + n + '.';
                        }
                        else
                        {
                            yield return '[' + resultPrefix + n + ']';
                        }
                    }
                }
            }

            protected Node EnsureNamespace(string ns)
            {
                EnsureDictionary();

                if (!_nodes.ContainsKey(ns))
                {
                    _nodes[ns] = new Node(ns, NodeKind.Namespace);
                }

                return _nodes[ns];
            }

            private void Add(Node node)
            {
                EnsureDictionary();

                _nodes[node.Name] = node;
            }

            private void EnsureDictionary()
            {
                if (_nodes == null)
                {
                    _nodes = new Dictionary<string, Node>(StringComparer.OrdinalIgnoreCase);
                }
            }
        }

        private enum NodeKind
        {
            Root = 0,
            Namespace = 1,
            Type = 2,
            NestedType = 3,
        }

        private sealed class RootNode : Node
        {
            public readonly object SyncRoot = new object();
            
            public RootNode() 
                : base(null, NodeKind.Root) 
            {
            }

            public Node AddNamespace(string name)
            {
                return EnsureNamespace(name);
            }
        }
    }
}
